import Axios, {AxiosInstance} from "axios";
import * as cofs from "fs-extra";
import * as path from "path";
import * as FormData from "form-data";

interface FileInfo {
	key: string;
	name: string;
	uid: string; // 用户ID
	pid?: string; // 父目录ID

	xtlink?: string; // 客户端分享链接
	_downloadurl?: string; // 下载链接
	_ctlink?: string; // 浏览器分享链接
}

export default class CTFile {
	private email: string;
	private password: string;
	private uid: string;
	private axios: AxiosInstance;
	private token: string;
	constructor(email: string, password: string, token?: string) {
		this.email = email;
		this.password = password;
		this.axios = Axios.create({headers: {"User-Agent": "okhttp/3.11.0"}});
		this.token = token;
	}

	async checkAndLogin(retry: number = 3): Promise<string> {
		if (!this.uid) {
			let url = await this.getUploadURL().catch((x: any) => "");
			let m = /userid=(\d+)/.exec(url);
			this.uid = m && m[1];
		}
		if (!this.uid) {
			if (retry <= 0) return "";
			console.log("login: ", await this.login());
			return this.checkAndLogin(retry - 1);
		}
		return this.uid ? this.token : "";
	}

	async request(path: string, data: any) {
		if (path[0] == "/") path = path.slice(1);
		var [namespace, item, action] = path.split("/");
		var url = `https://home.ctfile.com/mobiapi.php?session=${this.token}&namespace=${namespace}&item=${item}&action=${action}&os=0`;
		let ret = await this.axios.post(url, data);
		if (ret.data.code != 200) throw ret.data;
		return ret.data;
	}

	async login() {
		let data = await this.request("/user/user_act/login", {
			device_id: "msm8998",
			unique_id: "14d3b378f02eed85",
			carrier: "",
			app_version: "2.6.36",
			email: this.email,
			password: this.password
		});
		this.token = data.token;
		if (this.token) await cofs.writeFile("ctfile.cookie", this.token);
		return this.token;
	}

	/**
	 *
	 * @param {FileInfo} [file]
	 * @returns {Promise<FileInfo[]>}
	 */
	getFiles(file?: FileInfo): Promise<FileInfo[]> {
		if (!file) file = {key: "d0", uid: this.uid, name: ""};
		return this.request((file.xtlink ? "browser" : "public") + "/file_act/file_list", {
			xtlink: file.xtlink,
			start: 0,
			folder_id: file.key && file.key[0] == "d" ? file.key : "d0",
			orderby: "new",
			filter: "all",
			reload: false
		}).then(data => {
			return data.results.map((x: any) => {
				x.uid = file.uid || this.uid;
				x.pid = file.key;
				x.xtlink = file.xtlink;
				return x;
			});
		});
	}

	/**
	 * @param {FileInfo} file
	 * @returns {Promise<string>}
	 */
	getShareURL(file: FileInfo) {
		if (file._ctlink) return Promise.resolve(file._ctlink);
		if (file.key[0] === "f") return Promise.resolve((file._ctlink = `https://474b.com/file/${file.uid}-${file.key.slice(1)}`));
		return this.request((file.xtlink ? "browser" : "public") + "/file_act/file_share", {
			ids: [file.key]
		}).then(x => (file._ctlink = x.results[0].ctlinks));
	}

	/**
	 * @param {FileInfo} file
	 * @returns {Promise<string>}
	 */
	getDownloadURL(file: FileInfo) {
		if (file._downloadurl) return Promise.resolve(file._downloadurl);
		return this.request((file.xtlink ? "browser" : "public") + "/file_act/file_download_url", {
			xtlink: file.xtlink,
			file_id: file.key,
			usessl: 1,
			carrier: 0
		}).then(x => (file._downloadurl = x.downloadurl));
	}

	/**
	 * @param {string} folder_id
	 */
	getUploadURL(folder_id: string = "d0") {
		return this.request("public/file_act/file_upload", {
			folder_id: folder_id
		}).then(x => x.uploadurl);
	}

	/**
	 *
	 * @param {string} filename
	 * @param {string} [folder_id]
	 * @returns {Promise<FileInfo>}
	 */
	async uploadFile(filename: string, folder_id?: string): Promise<FileInfo> {
		let url = await this.getUploadURL(folder_id);
		let form = new FormData();
		let stat = await cofs.stat(filename);
		let name = path.basename(filename);
		form.append("filesize", stat.size);
		form.append("file", cofs.createReadStream(filename), name);
		let ret = await this.axios.post(url, form, {
			headers: form.getHeaders()
		});
		if (+ret.data) return {key: "f" + ret.data, name, uid: this.uid};
		throw ret.data;
	}

	/**
	 *
	 * @param {FileInfo} file
	 * @param {string} [folder_id]
	 * @returns {Promise<FileInfo>}
	 */
	async saveFile(file: FileInfo, folder_id: string = "d0") {
		return this.request((file.xtlink ? "browser" : "public") + "/file_act/file_save", {
			xtlink: file.xtlink,
			folder_id: folder_id,
			ids: [file.key]
		});
	}

	async deleteFile(ids: string | string[]) {
		return this.request("public/file_act/file_delete", {
			ids: Array.isArray(ids) ? ids : [ids]
		});
	}

	async getShareFiles(share_url: string, passcode?: string) {
		var f = path.basename(share_url);
		var [uid, fid] = f.split("-");
		if (!+uid || !+fid) throw `wrong share_url: ${share_url}`;
		var ret = await this.axios.get(`https://webapi.400gb.com/getfile.php?f=${f}&passcode=${passcode || ""}`, {headers: {Origin: "https://545c.com"}});
		var file_chk = ret.data.file_chk;
		var url = `https://webapi.400gb.com/get_file_url.php?uid=${uid}&fid=${fid}&folder_id=0&file_chk=${file_chk}&mb=0&app=1&acheck=1&verifycode=&rd=${Math.random()}`;
		var ret = await this.axios.get(url, {
			headers: {
				"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36"
			}
		});
		var xtlink = "ctfile://xturl" + ret.data.xt_link;
		return await this.getFiles({key: "", name: "", xtlink, uid});
	}
}
